Skip to content

fix(#210): replayed or cookie-less OAuth callbacks redirect home instead of 500 - #211

Open
sspickle wants to merge 1 commit into
masterfrom
fix/auth-callback-500
Open

fix(#210): replayed or cookie-less OAuth callbacks redirect home instead of 500#211
sspickle wants to merge 1 commit into
masterfrom
fix/auth-callback-500

Conversation

@sspickle

@sspickle sspickle commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Fixes #210.

What

/google/auth catches OAuthError (the base of MismatchingStateError, also covering a replayed code's invalid_grant) and redirects to / — the only place a user whose callback can never succeed can actually do something useful. The no-state branch ("Yikes!") now redirects too instead of falling through to a guaranteed crash.

Why redirect rather than an error page

The state is single-use by design (CSRF protection — untouched here). Once it's consumed or the cookie never existed, retrying the same URL cannot work; the only recovery is starting a fresh sign-in from the landing page. A bare 500 invites exactly the wrong response — refresh — which is how one classroom machine produced 68 consecutive failures on 2026-09-01.

Testing

tests/test_auth_callback.py reproduces the exact production exception (RED on master: MismatchingStateError: mismatching_state: CSRF Warning!) and pins the redirect for both the replay shape and the bare-URL shape.

Suite run under python:3.12 (matching the GAE runtime, since a 3.13 venv can no longer import the app — cgi was removed): 21 passed, 2 failed — both failures pre-exist on master in test_plotusers and are unrelated (verified by running master in the same container).

Production data

Steady 1–2% of sign-ins have failed this way for as far back as retention reaches, across a July-15 deploy boundary and a 6× traffic change — full numbers in #210.

… 500

The authlib state is single-use and lives in the session cookie, so two real
populations hit MismatchingStateError on /google/auth and got GAE's bare
'500 Server Error' page:

  - anyone who REFRESHES the callback URL: the state was consumed on the first
    attempt, so every retry can only fail. Observed live 2026-09-01: one
    classroom machine retried a dead callback 68 times.
  - browsers that refuse the session cookie, so no state is ever stored.

Production logs show a steady 1-2% of sign-ins failing this way for at least a
month (as far back as retention goes), across ~150/day summer traffic and
~900+/day semester-start traffic alike. Nothing changed server-side; the
semester surge just made a chronic failure loud.

Catch OAuthError (MismatchingStateError's base, which also covers a replayed
code's invalid_grant) and redirect to '/', where the user can simply sign in
again — the only action that can ever work. The bare /google/auth-with-no-state
branch ('Yikes!') now redirects too instead of falling through to a guaranteed
crash.

Tests reproduce the exact production exception (RED on master) and pin the
redirect. Suite run under python:3.12 (the GAE runtime): 21 passed, 2 failed —
both failures pre-exist on master in test_plotusers, unrelated.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sign-in callback 500s: replayed/cookie-less /google/auth throws MismatchingStateError as a bare GAE error page

1 participant